﻿using System;
using System.Runtime.InteropServices;
using System.Drawing;

namespace DirectDrawWrapper
{
    public class Surface : IDisposable
    {
        [DllImport("DirectDrawWrapperUnmanaged.dll")]
        private unsafe static extern uint Blt(void* destSurface, ref tagRECT dest, void* sourceSurface, ref tagRECT src, uint flags, void* fx);

        [DllImport("DirectDrawWrapperUnmanaged.dll")]
        private unsafe static extern uint Blt(void* destSurface, void* dest, void* sourceSurface, void* src, uint flags, void* fx);

        [DllImport("DirectDrawWrapperUnmanaged.dll")]
        private unsafe static extern void Fill(void* surface, uint color);

        private DirectDrawGraphics _directDraw;
        public DDSURFACEDESC _desc;
        internal IDDrawSurface _surface;
        internal unsafe IntPtr _surfacePtr;
        private bool _isDisposed;

        internal Surface(DirectDrawGraphics draw, IDDrawSurface surface)
        {
            _directDraw = draw;
            _surface = surface;
            _surfacePtr = Marshal.GetComInterfaceForObject(surface, Type.GetTypeFromCLSID(new Guid("0b0e83e4-f37f-11d2-8b15-00c04f689292")));
            var desc = new DDSURFACEDESC();
            desc.dwSize = (uint)Marshal.SizeOf(desc);
            _surface.GetSurfaceDesc(ref desc);
            _desc = desc;
        }

        ~Surface()
        {
            OnDispose();
        }

        public void Fill(Color color)
        {
            unsafe
            {
                Fill((void*)this._surfacePtr, ColorUtil.GetColor(ref color, ref _desc.ddpfPixelFormat));
            }
        }

        public void Fill(uint color)
        {
            uint retval = 0;
            var fx = new DDBLTFX { dwSize = DDBLTFX.Size, dwFillColor = color };

            unsafe
            {
                var desc = new DDSURFACEDESC();
                desc.dwSize = (uint)Marshal.SizeOf(desc);
                _surface.GetSurfaceDesc(ref desc);

                retval = _surface.Blt(null, null, null, BltFlags.DDBLT_COLORFILL, (void*)&fx);

                if (retval != 0)
                    throw ExceptionUtil.Create(retval);
            }
        }

        public void SetColorKey(Color color, ColorKeyFlags flags)
        {
            var colorKey = new DDCOLORKEY();
            var format = _desc.ddpfPixelFormat;
            var colorValue = color;

            colorKey.dwColorSpaceHighValue = ColorUtil.GetColor(ref colorValue, ref format);
            colorKey.dwColorSpaceLowValue = colorKey.dwColorSpaceHighValue;

            _surface.SetColorKey(flags, ref colorKey);
        }
        public void Draw(ref tagRECT destRectangle, Surface source, ref tagRECT sourceRectangle, BltFlags flags)
        {
            try
            {
                tagRECT destREC = destRectangle;
                destREC.right = Math.Min(destREC.right, Width);
                destREC.bottom = Math.Min(destREC.bottom, Height);
                destREC.top = Math.Max(0, destREC.top);
                destREC.left = Math.Max(0, destREC.left);

                tagRECT srcREC = sourceRectangle;
                unsafe
                {
                    //Blt(void* lpDestRect, IDDrawSurface lpDDSrcSurface, void* lpSrcRect, BltFlags dwFlags, void* lpDDBltFx);
                    //Blt(void* lpDestRect, IDDrawSurface lpDDSrcSurface, void* lpSrcRect, BltFlags dwFlags, void* lpDDBltFx);

                    var result = Blt((void *)this._surfacePtr, ref destREC, (void *) source._surfacePtr, ref srcREC, (uint)flags, (void*)0);
                    //var result = _surface.Blt((void*)&destREC, source._surface, (void*)&srcREC, flags, (void*)0);
                    //May want to revisit, according to docs is only 10% faster.
                    //var result = _surface.BltFast(destREC.left, destREC.top, null, (void*)&srcREC, flags);
                    if (result != 0)
                        throw ExceptionUtil.Create(result);
                }
            }
            catch(Exception)
            {
                _surface.Restore();
                var result = source._surface.Restore();
                if (result != 0)
                    throw ExceptionUtil.Create(result);

                source._surface.GetSurfaceDesc(ref source._desc);

                if (source._surface.IsLost() != 0)
                    throw new Exception("Could not restore surface");
            }
        }
        public void Dispose()
        {
            if (_isDisposed)
                return;

            _isDisposed = true;
            OnDispose();
            GC.SuppressFinalize(this);
        }

        private void OnDispose()
        {
            if (_surface != null)
            {
                Marshal.FinalReleaseComObject(_surface);
                _surface = null;
            }
        }

        public uint GetDC(ref IntPtr lphDC)
        {
            try
            {
                return _surface.GetDC(ref lphDC); ;
            }
            catch (Exception)
            {                
                try
                {
                    _surface.Restore(); 
                    return _surface.GetDC(ref lphDC); ;
                }
                catch (Exception)
                {
                    return 0xff;
                }
            }
        }

        public uint RelaseDC(IntPtr hDC)
        {
            return _surface.ReleaseDC(hDC);
        }

        internal void LoadImage(Bitmap image)
        {
            var hBitmap = image.GetHbitmap();
            var hdc = IntPtr.Zero;

            var hdcImage = GDI.CreateCompatibleDC(IntPtr.Zero);
            GDI.SelectObject(hdcImage, hBitmap);

            _surface.GetDC(ref hdc);

            GDI.BitBlt(hdc, 0, 0, image.Width, image.Height, hdcImage, 0, 0, TernaryRasterOperations.SRCCOPY);
            _surface.ReleaseDC(hdc);
            GDI.DeleteDC(hdcImage);

            GDI.DeleteObject(hBitmap);
        }

        public int Width { get { return (int)_desc.dwWidth; } }
        public int Height { get { return (int)_desc.dwHeight; } }
    }
}
